iBeaconでcocos2dと連携する方法 | アドカレ2013 : SP #18
自己紹介
初めまして、来年度からクラスメソッドで勤務予定の荒川 靖久と申します。
現在はインターンでiOS関係のお仕事をさせて頂いております。ブログ記事もiOSの話題中心になるかと思います。
以前からゲームアプリを作っていたこともあり、ゲーム関連の記事も書けたら良いなと考えています。特にcocos2d frameworkが好きでcocos2d-xの勉強会に最近は出没したりします。
先日【iOS勉強会】iBeaconでできること – Developers.IO Meetup 02の最後のLT枠でiBeaconとcocos2dを絡めたネタをお話しさせて頂きました。
iBeaconは現在多くのiPhoneアプリ開発者から注目を浴びている技術の一つではありますが、まだまだ提案や試作段階の物が多いです。試してみたはいいけれども、よくわからなくてすぐ諦めたという方もいるかもしれません。
そこで数多くいるフロントエンジニア・ゲームエンジニアの方にiBeaconにもっと興味を持ってもらいたいということで今回はLTで紹介したアプリを一から作成する手順を紹介します。
開発環境構築(Macのみ)
- Xcodeのインストール
- Provisioningファイルに登録されたテストデバイス(実機2台)を用意
- cocos2d for iPhoneのインストール
Xcodeのインストールはこちらから行って下さい。
実機の用意は省略します。
cocos2d for iPhoneは公式サイトのDownloadのタブからOlder versions Stable versionの箇所にあるcocos2d-iphone-2.1.tar.gzを任意のディレクトリにダウンロードし、ダブルクリックして解凍します。解凍したディレクトリをFinderで見てみます。
そしてターミナル.appを起動(見つからなければmac画面右上の虫眼鏡アイコンを押し、出て来た検索ウィンドウに「ターミナル」と入力しEnter)し、
「cd(半角スペース)」と打ちます。
その後Finderで表示されているディレクトリをターミナルへドラッグ&ドロップします。
cd のあとに~/(ダウンロードしたファイルの場所)/cocos2d-iphoneと表示されたらEnterを押します。
その後「./install-templates.sh」と入力してEnterを押し、インストール完了(ok)のメッセージが表示されれば正常にインストールができています。
これでXcodeのプロジェクトからcocos2d for iPhoneのテンプレートが選択出来るようになります。
プロジェクトを作成する(Hello World)
Xcodeを起動し、Welcome to Xcodeと表示されたら、その下にある「Create a new Xcode project」を選択して下さい。
ここで正常にcocos2dのインストールが行えていればiOSの欄に「cocos2d v2.x」の項目が追加されているはずです。
選択したら一番左の「cocos2d iOS」を選択しNextを押します。以降は通常のプロジェクト作成と同じなので省略します。
プロジェクトが作成できたらまずXcodeメニュー画面左上のRunボタンを押すとこのように表示されます。
これでcocos2dの基本的な導入はおしまいです。
次にcocos2dのプロジェクトにCoreLocation.frameworkを導入します。
これでiBeaconを実装するための準備が整いました。
バックグランドでも処理を続けたい場合はGeneralの隣のタブ、Capabillitiesのバックグラウンドモードをオンにして、Location updatesにチェックを入れます。
今回は標準で用意されているHelloWorldLayer.h(Hello Worldが表示されているシーン)にCoreLocation.frameworkを追加していきます。
#import <GameKit/GameKit.h> // When you import this file, you import all the cocos2d classes #import "cocos2d.h" #import <CoreLocation/CoreLocation.h> // HelloWorldLayer @interface HelloWorldLayer : CCLayer <GKAchievementViewControllerDelegate, GKLeaderboardViewControllerDelegate, CLLocationManagerDelegate> { } @property (nonatomic, strong) CLLocationManager *locationManager_; @property (nonatomic, strong) NSUUID *proximityUUID_; @property (nonatomic, strong) CLBeaconRegion *beaconRegion_; // returns a CCScene that contains the HelloWorldLayer as the only child +(CCScene *) scene; @end
- #importを追加
- CLLocationManagerDelegateを追加
- propertyを3つ追加
を行いました。
各プロパティの説明については[iOS 7] 新たな領域観測サービス iBeacon を使ってみるに詳細が書かれていますので、気になる方は参考にして下さい。
次にHelloWorldLayer.mに処理を記述していきます。
// Import the interfaces #import "HelloWorldLayer.h" // Needed to obtain the Navigation Controller #import "AppDelegate.h" #pragma mark - HelloWorldLayer // ターミナルから $ uuidgen static NSString * const kBeaconUUID = @"AA9D2576-7392-4763-AB84-6C30F8A8D366"; // 任意の識別子 static NSString * const kBeaconIdentifier = @"com.classmethod.testCocosBeacon"; // 建物などの識別子(16ビットまでの任意の自然数) static NSUInteger const kTargetBeaconMajor = 1; // 区画などの識別子(16ビットまでの任意の自然数) static NSUInteger const kTargetBeaconMinor = 1; // HelloWorldLayer implementation @implementation HelloWorldLayer // Helper class method that creates a Scene with the HelloWorldLayer as the only child. +(CCScene *) scene { // 'scene' is an autorelease object. CCScene *scene = [CCScene node]; // 'layer' is an autorelease object. HelloWorldLayer *layer = [HelloWorldLayer node]; // add layer as a child to scene [scene addChild: layer]; // return the scene return scene; } // on "init" you need to initialize your instance -(id) init { // always call "super" init // Apple recommends to re-assign "self" with the "super's" return value if( (self=[super init]) ) { // create and initialize a Label CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64]; // ask director for the window size CGSize size = [[CCDirector sharedDirector] winSize]; // position the label on the center of the screen label.position = ccp( size.width /2 , size.height/2 ); // add the label as a child to this Layer [self addChild: label]; // // Leaderboards and Achievements // // Default font size will be 28 points. [CCMenuItemFont setFontSize:28]; // to avoid a retain-cycle with the menuitem and blocks __block id copy_self = self; // Achievement Menu Item using blocks CCMenuItem *itemAchievement = [CCMenuItemFont itemWithString:@"Achievements" block:^(id sender) { GKAchievementViewController *achivementViewController = [[GKAchievementViewController alloc] init]; achivementViewController.achievementDelegate = copy_self; AppController *app = (AppController*) [[UIApplication sharedApplication] delegate]; [[app navController] presentModalViewController:achivementViewController animated:YES]; [achivementViewController release]; }]; // Leaderboard Menu Item using blocks CCMenuItem *itemLeaderboard = [CCMenuItemFont itemWithString:@"Leaderboard" block:^(id sender) { GKLeaderboardViewController *leaderboardViewController = [[GKLeaderboardViewController alloc] init]; leaderboardViewController.leaderboardDelegate = copy_self; AppController *app = (AppController*) [[UIApplication sharedApplication] delegate]; [[app navController] presentModalViewController:leaderboardViewController animated:YES]; [leaderboardViewController release]; }]; CCMenu *menu = [CCMenu menuWithItems:itemAchievement, itemLeaderboard, nil]; [menu alignItemsHorizontallyWithPadding:20]; [menu setPosition:ccp( size.width/2, size.height/2 - 50)]; // Add the menu to the layer [self addChild:menu]; [self startMonitoring]; } return self; } // on "dealloc" you need to release all your retained objects - (void) dealloc { // in case you have something to dealloc, do it in this method // in this particular example nothing needs to be released. // cocos2d will automatically release all the children (Label) // don't forget to call "super dealloc" [super dealloc]; } #pragma mark GameKit delegate -(void) achievementViewControllerDidFinish:(GKAchievementViewController *)viewController { AppController *app = (AppController*) [[UIApplication sharedApplication] delegate]; [[app navController] dismissModalViewControllerAnimated:YES]; } -(void) leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController { AppController *app = (AppController*) [[UIApplication sharedApplication] delegate]; [[app navController] dismissModalViewControllerAnimated:YES]; } #pragma mark - CLLocationManagerDelegate methods // 画面を表示した時に通知を受け取る - (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region { CCLOG(@"Determine State"); // 観測可能領域内かつ測定可能 if(state == CLRegionStateInside && [CLLocationManager isRangingAvailable]) { [self.locationManager_ startRangingBeaconsInRegion:self.beaconRegion_]; } } - (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region { CCLOG(@"Start Monitoring Region"); if([CLLocationManager isRangingAvailable]) { [self.locationManager_ startRangingBeaconsInRegion:self.beaconRegion_]; } } - (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region { CCLOG(@"Enter Region"); // 測定開始 if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) { [self.locationManager_ startRangingBeaconsInRegion:(CLBeaconRegion *)region]; } } - (void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region { CCLOG(@"Exit Region"); // 測定停止 if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) { [self.locationManager_ stopRangingBeaconsInRegion:(CLBeaconRegion *)region]; } } - (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region { CLBeacon *nearestBeacon = beacons.firstObject; CCLOG(@"%@", [NSString stringWithFormat:@"major:%@\n minor:%@\n accuracy:%f\n rssi:%d", nearestBeacon.major, nearestBeacon.minor, nearestBeacon.accuracy, nearestBeacon.rssi]); } - (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error { if(error.code == kCLErrorRegionMonitoringFailure) { CCLOG(@"Fail for Region"); } else { [[[UIAlertView alloc] initWithTitle:@"error" message:error.description delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show]; } } #pragma mark - Private methods // Beaconによる領域観測を開始 - (void)startMonitoring { if ([CLLocationManager isMonitoringAvailableForClass:[CLBeaconRegion class]]) { // CLLocationManagerの生成とデリゲートの設定 self.locationManager_ = [CLLocationManager new]; self.locationManager_.delegate = self; // 生成したUUIDからNSUUIDを作成 self.proximityUUID_ = [[NSUUID alloc] initWithUUIDString:kBeaconUUID]; // CLBeaconRegionを作成 self.beaconRegion_ = [[CLBeaconRegion alloc] initWithProximityUUID:self.proximityUUID_ major:kTargetBeaconMajor identifier:kBeaconIdentifier]; [self.locationManager_ startMonitoringForRegion:self.beaconRegion_]; } } // 指定ビーコンの測定・監視を止める - (void)stopMonitoring:(CLBeaconRegion *)region { if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) { [self.locationManager_ stopMonitoringForRegion:self.beaconRegion_]; [self.locationManager_ stopRangingBeaconsInRegion:region]; self.locationManager_.delegate = nil; self.locationManager_ = nil; self.proximityUUID_ = nil; self.beaconRegion_ = nil; } } @end
以下を順番に実装します。
実機でデバッグ
ここで実行する時の注意点を挙げます。
上記を満たして成功すると
こんな感じにログが表示されます。これでPeripheralデバイス(Beacon送信側)との距離をBluetooth Low Energyで測定し、ログを表示することができるようになりました。iBeaconの基本的な実装はこれでおしまいです。
更に発展させるにはiBeacon Advent Calender - iBeaconとcocos2dを連携してみる - iBeaconでゲームアプリを作ってみたに書きましたので、興味を持って頂いた方はご覧になってみて下さい。